﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using COFE.Core;
using COFE.Core.Lister;
using COFE.Core.Profile;
using NUnit.Framework;
using COFE.Core.Filter;
using COFE.Core.Entry;

namespace COFE.Data.Lister
{
    public class DBDirectoryListerBase : DirectoryListerBase, IDirectoryLister2
    {
        #region Constructor

        protected DBDirectoryListerBase(IDirectoryLister cachingDirectoryLister)
            : base(cachingDirectoryLister.Factory, cachingDirectoryLister.ListerKey)
        {
            Assert.IsNotNull(cachingDirectoryLister);
            _cachingDirectoryLister = cachingDirectoryLister;

            _expand = (entry, tempPath) => { cachingDirectoryLister.Expand((IInternalFileSystemInfoExA)entry); };
            _getParent = (entry) =>
                {
                    Guid parentGuid = (entry as IInternalFileSystemInfoExA).ParentId;
                    if (parentGuid == Guid.Empty)
                        return null;
#if DEBUG
                    //Check if the parent dbEntry is exists in table.                    
                    Assert.IsTrue(COFEDBShared.DBEntryManager.Exists(parentGuid));
#endif
                    var retVal = (IInternalDirectoryInfoExA)COFEShared.PathParser
                        .TryParsePath(parentGuid.ToString(), ParsableType.Guid, EntryConstructionFlags.Default);

                    Assert.IsNotNull(retVal);

                    return retVal;
                };

            _getPosition = (entry) => { return cachingDirectoryLister.GetPosition((IInternalFileSystemInfoExA)entry); };
            _setPosition = (entry, pos) => { cachingDirectoryLister.SetPosition((IInternalFileSystemInfoExA)entry, pos); };
        }

        #endregion

        #region Methods

        public override IDirectoryController CreateController()
        {
            return _cachingDirectoryLister.CreateController();
        }

        public override IStreamProvider CreateStreamProvider()
        {
            return _cachingDirectoryLister.CreateStreamProvider();
        }

        private bool expiredList(DateTime refreshTIme)
        {
            return refreshTIme < DateTime.UtcNow.Subtract(COFEDBShared.TTLEntryList);
        }

        private bool expiredRefresh(DateTime refreshTIme)
        {
            return refreshTIme < DateTime.UtcNow.Subtract(COFEDBShared.TTLEntryExist);
        }

        private IEnumerable<IFileSystemInfo> listOrCreateCache(IInternalDirectoryInfoExA parent, EntryConstructionFlags flags)
        {
            IPropertyProvider[] pendingToWrite = new IPropertyProvider[] { };
            Guid parentId = Guid.Empty;
            Entry parentEntry = null;


            COFEDBShared.DBOperations.UpdateDatabase(
                (context) => COFEDBShared.DBEntryManager.LookupEntry(context, parent.ParseName),
                (context, dbParent) =>
                {
                    //Need update here.
                    if (dbParent == null || expiredRefresh(dbParent.RefreshTime)) //If expired or not exists, create it.
                        dbParent = COFEDBShared.DBEntryManager.UpdateEntry(context,
                            parent.PrimaryPropertyProvider);

                    DateTime lastListTime = (DateTime)COFEDBShared.DBEntryPropertyManager
                        .GetProperty(context, dbParent, DatabaseProperties.LastListTime);

                    if (expiredList(lastListTime))
                    {
                        try
                        {
                            pendingToWrite = (from exa in _cachingDirectoryLister.List(parent,
                                         EntryConstructionFlags.DefaultWithoutCache)
                                              select COFEShared.VolumeRegistrar.ConvertEntry(exa.InternalProperties.PrimaryPropertyProvider,
                                              EntryConstructionFlags.None)).ToArray();
                        }
                        catch (System.IO.FileNotFoundException)
                        {
                            //Nothing to write.
                            pendingToWrite = new IPropertyProvider[] { };
                        }
                    }

                    parentId = dbParent.EntryId;

                    //Update to database
                    if (pendingToWrite.Length > 0)
                        COFEDBShared.DBEntryManager.UpdateEntry(context, pendingToWrite, true, true);
                    else COFEDBShared.DBEntryManager.UpdateDirectoryIsEmpty(context, parent.InternalProperties.PrimaryPropertyProvider);

                });

            parentEntry = COFEDBShared.DBOperations.ReadDatabase(
                (context) => context.Entries.Include("Children")
                        .FirstOrDefault(e => e.EntryId == parentId)
                        );

            if (parentEntry != null)
            {
                Assert.IsTrue(parentEntry.Children.IsLoaded);
                foreach (var subDbEntry in parentEntry.Children)
                    yield return
                        COFEShared.EntryConstructor.CreateEntry(subDbEntry.SecondaryVolumeId,
                    new DBCachePropertyProvider(subDbEntry), flags);
            }


        }

        public override IEnumerable<IFileSystemInfo> List(IInternalDirectoryInfoExA parent, EntryConstructionFlags flags)
        {
            if (!flags.HasFlag(EntryConstructionFlags.EnableCache))
                return base.List(parent, flags);
            else
                return listOrCreateCache(parent, flags);
        }

        private IEnumerable<T> listEntries<T>(COFEDB2 context,
            IEntryFilter[] entryFilters, IOptionFilter[] options,
            EntryConstructionFlags flags)
            where T : IFileSystemInfo
        {
            return from dbEntry in
                       COFEDBShared.DBEntryOperations.ReadEntries(context,
                       entryFilters, options)
                   let e = COFEShared.EntryConstructor.CreateEntry(
                              dbEntry.SecondaryVolumeId,
                              new DBCachePropertyProvider(dbEntry), flags)
                   where e is T
                   select (T)e;
        }

        //IDirectoryLister2.ListAll()
        public bool ListAll<T>(IInternalDirectoryInfoExA parent,
             IEntryFilter[] entryFilters, IOptionFilter[] options,
             EntryConstructionFlags flags,
             out IEnumerable<T> output)
             where T : IFileSystemInfo
        {
            output = null;

            var source = options.GetSearchSource();

            //Dont use database if outdated or even uncached.
            if (DateTime.UtcNow.Subtract(parent.GetProperty(DatabaseProperties.LastListTime).ValueAsUtcDateTime) >
                COFEDBShared.TTLEntryList)
                return false;

            if (!(source.HasFlag(SearchSourceType.Cache)))
                return false;            

            //Check for listing expiration, if require to search from Filesystem
            if (source.HasFlag(SearchSourceType.FileSystem))
            {
                bool rootExpired = (parent.InternalProperties.GetProperty(
                    DatabaseProperties.LastListTime).ValueAsUtcDateTime <
                        DateTime.UtcNow.Subtract(COFEDBShared.TTLEntryList));

                entryFilters = entryFilters.Concat(
                    new List<IEntryFilter>()
                {
                    new COFE.Core.Filter.RootEntryFilter(parent)
                }).ToArray();

                bool someEntriesExpired = rootExpired ||
                    COFEDBShared.DBOperations.ReadDatabase(
                    (c) =>
                    {
                        return COFEDBShared.DBEntryOperations.ReadEntries(c,
                            new IEntryFilter[] { 
                            new COFE.Core.Filter.RootEntryFilter(parent),
                            new COFE.Data.Filter.DBExpireEntryFilter(DBExpiredType.List)
                        }, options).Any();
                    });

                if (someEntriesExpired)
                    return false;
            }

             IEnumerable<Entry> entries = COFEDBShared.DBOperations.ReadDatabase(
                 (c) =>
                 {
                     return COFEDBShared.DBEntryOperations.ReadEntries(c,  entryFilters, options).ToList();
                 });


            output = from dbEntry in entries
                         let e = COFEShared.EntryConstructor.CreateEntry(
                                    dbEntry.SecondaryVolumeId,
                                    new DBCachePropertyProvider(dbEntry), flags)
                         where e is T
                         select (T)e;
            
            return true;
        }

        #endregion

        #region Data
        protected IDirectoryLister _cachingDirectoryLister;

        #endregion

        #region Public Properties


        #endregion






    }
}
